package com.xuecheng.media.service.jobhandler;

import com.xuecheng.base.utils.Mp4VideoUtil;
import com.xuecheng.base.utils.VideoUtil;
import com.xuecheng.media.mapper.MediaProcessMapper;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.MediaFileService;
import com.xuecheng.media.service.MediaProcessService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import io.minio.errors.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
@RequiredArgsConstructor
public class VideoTask {

    private final MediaProcessService mediaProcessService;

    private final MediaFileService mediaFileService;

    private final MinioClient minioClient;

    @Value("${videoprocess.ffmpegpath}")
    private String ffmpeg_path;

    /**
     * 分片广播处理视频转码
     */
    @XxlJob("videoJobHandler")
    public void videoJobHandler() throws Exception {
        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        List<MediaProcess> mediaProcesses = null;
        int size = 0;

        try {
            //获取cpu的线程数
            int count = Runtime.getRuntime().availableProcessors();

            //获取任务列表
            mediaProcesses = mediaProcessService.selectListByShardIndex(shardTotal, shardIndex, count);

            size = mediaProcesses.size();
            log.debug("任务数量:{}", size);
            if (size <= 0) {
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        //开启线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(size);
        //计数器
        CountDownLatch countDownLatch = new CountDownLatch(size);
        //任务处理
        mediaProcesses.forEach(mediaProcess -> {
            threadPool.execute(() -> {//开启线程
                // 线程执行逻辑
                try {
                    //先抢占任务
                    Boolean b = mediaProcessService.startTask(mediaProcess.getId());
                    if (!b) {
                        log.debug("抢占任务失败,任务id为{}", mediaProcess.getId());
                        return;
                    }
                    log.debug("执行任务:{}", mediaProcess);

                    //任务id
                    Long taskId = mediaProcess.getId();
                    //获取桶
                    String bucket = mediaProcess.getBucket();
                    //文件路径
                    String filePath = mediaProcess.getFilePath();
                    //获取源文件的md5
                    String fileId = mediaProcess.getFileId();
                    //首先要下载视频到本地
                    File file = mediaFileService.downloadFileFromMinIO(bucket, filePath);

                    //下载视频到本地失败
                    if (file == null) {
                        log.debug("下载视频到本地失败, 桶:{},文件路径:{}", bucket, filePath);
                        mediaProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "下载视频到本地失败");
                        return;
                    }

                    //创建mp4临时文件
                    File tempFile = null;
                    try {
                        tempFile = File.createTempFile("mp4", ".mp4");
                    } catch (IOException e) {
                        log.error("创建mp4临时文件失败");
                        mediaProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "创建mp4临时文件失败");
                        return;
                    }

                    //对于文件进行转码
                    Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path, file.getAbsolutePath(), tempFile.getName(), tempFile.getAbsolutePath());
                    String s = null;
                    try {
                        s = videoUtil.generateMp4();
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error("处理文件转码失败处理的文件路径为{},错误信息为{}", bucket + "/" + filePath, e.getMessage());
                        mediaProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "处理文件转码失败");
                    }
                    if (!s.equals("success")) {
                        log.error("处理文件转码失败,处理的文件路径为{},错误信息为{}", bucket + "/" + filePath, s);
                        mediaProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "处理文件转码失败");
                        return;
                    }

                    //转码后的文件路径
                    String objectName = getFilePath(fileId, ".mp4");
                    //获取上传文件的url
                    String url = "/" + bucket + "/" + objectName;


                    //上传文件到minio
                    try {
                        mediaFileService.addMediaFilesToMinIO(bucket, tempFile.getAbsolutePath(), "video/mp4", objectName);
                        //将url存储至数据，并更新状态为成功，并将待处理视频记录删除存入历史
                        mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "2", fileId, url, null);
                    } catch (Exception e) {
                        log.error("上传视频失败或入库失败,视频地址:{},错误信息:{}", bucket + "/" + objectName, e.getMessage());
                        //最终还是失败了
                        mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", fileId, null, "处理后视频上传或入库失败");
                    }

                    //删除临时文件
                    file.delete();
                    tempFile.delete();
                } finally {
                    countDownLatch.countDown();
                }

            });
        });
        //等待,给一个充裕的超时时间,防止无限等待，到达超时时间还没有处理完成则结束任务
        countDownLatch.await(30, TimeUnit.MINUTES);

    }

    private String getFilePath(String fileMd5, String fileExt) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }
}
